package com.spukmk3me.scene;

import com.spukmk3me.debug.Logger;
import com.spukmk3me.math.Matrix4f;
import com.spukmk3me.math.Rectangle;
import com.spukmk3me.math.Vector3f;
import com.spukmk3me.util.Util;
import com.spukmk3me.video.VideoAPI;

/**
 *  \details
 *  Root node always exists and cannot be dropped or detached.
 */
public abstract class Scene
{
    protected Scene( boolean useDefaultRenderingProcess )
    {
        if ( useDefaultRenderingProcess )
        {
            initDefaultRendering();
            m_useDefaultRendering = true;
        }
    }
    
    public abstract Node createNode();
    public abstract Node createNode( String name );
    
    public abstract Node getRoot();
    public abstract Node getNode( String name );
    
    public abstract void insertNodeAfter( Node node, Node predecessor );
    public abstract void attachNode( Node parent, Node child );
    public abstract void detachNode( Node node );
    public abstract Node detachNode( String name );
    
    public abstract void dropNode( Node node );
    public abstract Node dropNode( String name );
    public abstract void dropChildren( Node node );
    public abstract Node dropChildren( String name );
    
    public void setVideoAPI( VideoAPI vapi )
    {
        m_vapi = vapi;
    }
    
    public VideoAPI getVideoAPI()
    {
        return m_vapi;
    }
    
    /**
     *  Render all scene and apply animator's animation.
     *  \details Nodes are rendered in parent-first order. Animator's works
     *  are applied after the node is rendered.\n
     *  Override this implementation if you find it not sufficient.
     *  @param deltaTime
     */
    public void render( int deltaTime )
    {
        if ( m_useDefaultRendering )
            render( getRoot(), deltaTime );
    }

    /**
     *  Render a node and its children
     *  @param node Top node to render.
     *  @param trans Transformation applied before rendering node. Null for an identity transformation.
     */
    private void render( Node node, int deltaTime )
    {
        Node        iterator;
        int         topStack = 0;

        m_nodeS[ 0 ]            = node;
        m_planeColorS[ 0 ]      = 0xFFFFFFFF;
        m_additionalColorS[ 0 ] = 0;
        m_animatingS[ 0 ]       = true;
        m_clipS[ 0 ].set( 0, 0, m_vapi.getScreenWidth(), m_vapi.getScreenHeight() );
        m_useBlending           = node.getBlendingUsage();
        m_vapi.setBlending( m_useBlending );
        
        while ( topStack != -1 )
        {
            if ( m_nodeS[ topStack ] == null )
            {
                --topStack;
                
                if ( topStack >= 0 )
                {
                    if ( !m_clipS[ topStack + 1 ].isEquals( m_clipS[ topStack ] ) )
                        m_vapi.setClip( m_clipS[ topStack ] );
                    
                    m_vapi.rollbackMatrix();
                }
            }
            else
            {
                iterator = m_nodeS[ topStack ];
                m_nodeS[ topStack ] = ( iterator.isLastSibling() )? null : iterator.next();

                if ( (iterator.getFlags() & Node.ENABLE) != 0 )
                {
                    ++topStack;
                    m_planeColorS[ topStack ] = Util.mulColor( m_planeColorS[ topStack - 1 ], iterator.getPlaneColor() );
                    m_additionalColorS[ topStack ] = Util.addColor( m_additionalColorS[ topStack - 1 ], iterator.getAdditionalColor() );
                    m_vapi.setPlaneColor( m_planeColorS[ topStack ] );
                    m_vapi.setAdditionalColor( m_additionalColorS[ topStack ] );
                    m_vapi.setBlending( iterator.getBlendingUsage() );
                    
                    iterator.getMatrix( m_tempNodeTransformation );
                    m_vapi.mulMatrix( m_tempNodeTransformation );
                    
                    if ( iterator.getClippingUsage() )
                    {
                        m_vapi.getCurrentMatrix( m_tempCurrentMatrix );
                        iterator.getClip( m_clipS[ topStack ] );
                        findNextClipping( m_clipS[ topStack - 1 ], m_clipS[ topStack ], m_tempCurrentMatrix );
                        m_vapi.setClip( m_clipS[ topStack ] );
                    }
                    else
                        m_clipS[ topStack ].copy( m_clipS[ topStack - 1 ] );
                    
                    m_animatingS[ topStack ] = m_animatingS[ topStack - 1 ] & iterator.getAnimating();

                    if ( (iterator.getFlags() & Node.VISIBLE) != 0 )
                    {
                        if ( iterator.getEntity() != null )
                            m_vapi.render( iterator.getEntity(), m_animatingS[ topStack ] );
                    }
                    
                    //////////////////////////////////////////
                    
                    if ( m_animatingS[ topStack ] )
                        iterator.runAnimators( deltaTime );
                    
                    //////////////////////////////////////////
                    
                    m_nodeS[ topStack ] = ( iterator.hasChildren() )? iterator.child() : null;
                }
            }
        }
    }
    
    private void changeStackSize( int newStackSize )
    {
        if ( m_nodeS != null )
        {
            /* $if SPUKMK3ME_DEBUG$ */
            if ( newStackSize < m_nodeS.length )
                Logger.trace( "New length is lower than current length." );
            /* $endif$ */
            
            if ( newStackSize > m_nodeS.length )
            {
                Object[]    dump;
                int[]       dump2;
                boolean[]   dump3;
                
                dump = new Node[ newStackSize ];
                System.arraycopy( m_nodeS, 0, dump, 0, m_nodeS.length );
                m_nodeS = (Node[])dump;
                
                dump2 = new int[ newStackSize + 1 ];
                System.arraycopy( m_planeColorS, 0, dump2, 0, m_planeColorS.length );
                m_planeColorS = dump2;
                
                dump2 = new int[ newStackSize + 1 ];
                System.arraycopy( m_additionalColorS, 0, dump2, 0, m_additionalColorS.length );
                m_additionalColorS = dump2;
                
                ///////////////////////////
                
                dump = new Rectangle[ newStackSize + 1 ];
                System.arraycopy( m_clipS, 0, dump, 0, m_clipS.length );
                
                for ( int i = m_clipS.length; i != dump.length; ++i )
                    dump[ i ] = new Rectangle();

                m_clipS = (Rectangle[])dump;
                
                //////////////////////////
                
                dump3 = new boolean[ newStackSize + 1 ];
                System.arraycopy( m_animatingS, 0, dump3, 0, m_animatingS.length );
                m_animatingS = dump3;
            }
        }
        else
        {
            m_nodeS             = new Node[ newStackSize ];
            m_planeColorS       = new int[ newStackSize + 1 ];
            m_additionalColorS  = new int[ newStackSize + 1 ];
            m_animatingS        = new boolean[ newStackSize + 1 ];
            m_clipS             = new Rectangle[ newStackSize + 1 ];
            
            for ( int i = 0; i != newStackSize + 1; ++i )
                m_clipS[ i ] = new Rectangle();
        }
    }
    
    private void findNextClipping( Rectangle currentClip, Rectangle untransformedNextClip, Matrix4f trans )
    {
        Vector3f v1 = new Vector3f( untransformedNextClip.x, untransformedNextClip.y, 0f );
        Vector3f v2 = new Vector3f( untransformedNextClip.x + untransformedNextClip.w,
            untransformedNextClip.y + untransformedNextClip.h, 0f );
        
        v1.mul( trans );
        v2.mul( trans );
        
        if ( v1.x > v2.x )
        {
            float temp = v1.x;
            
            v1.x = v2.x;
            v2.x = temp;
        }
        
        if ( v1.y > v2.y )
        {
            float temp = v1.y;
            
            v1.y = v2.y;
            v2.y = temp;
        }
        
        v1.x = Math.max( v1.x, currentClip.x );
        v1.y = Math.max( v1.y, currentClip.y );
        v2.x = Math.min( v2.x, currentClip.x + currentClip.w );
        v2.y = Math.min( v2.y, currentClip.y + currentClip.h );
        
        untransformedNextClip.x = (int)v1.x;
        untransformedNextClip.y = (int)v1.y;
        untransformedNextClip.w = (int)(v2.x - v1.x) + 1;
        untransformedNextClip.h = (int)(v2.y - v1.y) + 1;
    }
    
    private void initDefaultRendering()
    {
        changeStackSize( DEFAULT_STACK_SIZE );
        m_tempNodeTransformation = new Matrix4f();
        m_tempCurrentMatrix = new Matrix4f();
    }

    /**
     *  Root node's name.
     */
    public static final String ROOT = "root";
    
    private static final int    DEFAULT_STACK_SIZE = 32;
    
    private VideoAPI        m_vapi; 
    private Node[]          m_nodeS; // Node stack
    private int[]           m_planeColorS, m_additionalColorS;
    private boolean[]       m_animatingS;
    private Rectangle[]     m_clipS;
    private Matrix4f        m_tempNodeTransformation, m_tempCurrentMatrix;
    private boolean         m_useBlending;
    
    private boolean         m_useDefaultRendering;
}
